package fr.inserm.umr915.libjson;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Comparator;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * NodeFactory
 */
public class NodeFactory
	{
	private NilNode nilNode;
	private BooleanNode.TrueNode trueNode;
	private BooleanNode.FalseNode falseNode;
	private Comparator<String> stringComparator;
	
	
	public NodeFactory()
		{
		this.stringComparator=new Comparator<String>()
			{
			@Override
			public int compare(String o1, String o2) {
				return o1.trim().compareToIgnoreCase(o2.trim());
				}
			};
		
		this.nilNode=new NilNode()
			{
			@Override
			public NodeFactory getNodeFactory() {
				return NodeFactory.this;
				}
			};
		this.trueNode=new BooleanNode.TrueNode()
			{
			@Override
			public NodeFactory getNodeFactory() {
				return NodeFactory.this;
				}
			};
			
		this.falseNode=new BooleanNode.FalseNode()
			{
			@Override
			public NodeFactory getNodeFactory() {
				return NodeFactory.this;
				}
			};
		}
	
	public Comparator<String> getStringComparator()
		{
		return stringComparator;
		}
	
	public NilNode createNilNode()
		{
		return nilNode;
		}
	
	public BooleanNode createBooleanNode(boolean test)
		{
		return test?createTrueNode():createFalseNode();
		}
	
	public BooleanNode createBooleanNode(String test)
		{
		return createBooleanNode(Boolean.parseBoolean(test));
		}
	
	public BooleanNode.TrueNode createTrueNode()
		{
		return trueNode;
		}
	public BooleanNode.FalseNode createFalseNode()
		{
		return falseNode;
		}
	
	public IntNode createIntNode(int value)
		{
		return new IntNode(value)
			{
			@Override
			public NodeFactory getNodeFactory() {
				return NodeFactory.this;
				}
			};
		}
	
	public IntNode createIntNode(String value)
		{
		return createIntNode(Integer.parseInt(value));
		}
	
	public FloatNode createFloatNode(double value)
		{
		return new FloatNode(value)
			{
			@Override
			public NodeFactory getNodeFactory() {
				return NodeFactory.this;
				}
			};
		}
	
	public FloatNode createFloatNode(String value)
		{
		return createFloatNode(Double.parseDouble(value));
		}
	
	public StringNode createStringNode(String value)
		{
		return new StringNode(value)
			{
			@Override
			public NodeFactory getNodeFactory() {
				return NodeFactory.this;
				}
			};
		}
	
	public ArrayNode createArrayNode(int capacity)
		{
		return new ArrayNode(capacity)
			{
			@Override
			public NodeFactory getNodeFactory() {
				return NodeFactory.this;
				}
			};
		}
	
	public ArrayNode createArrayNode()
		{
		return createArrayNode(10);
		}
	
	public ObjectNode createObjectNode(int capacity)
		{
		return new ObjectNode(capacity)
			{
			@Override
			public NodeFactory getNodeFactory() {
				return NodeFactory.this;
				}
			};
		}
	
	
	public Node cast(String value)
		{
		if(value==null || value.equalsIgnoreCase("null") || value.equalsIgnoreCase("nil"))
			{
			return createNilNode();
			}
		
		if(value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false"))
			{
			return createBooleanNode(value.toLowerCase());
			}
		
		try {
			int v=Integer.parseInt(value);
			return createIntNode(v);
			} 
		catch (Exception e)
			{
			}
		try {
			Double v=Double.parseDouble(value);
			return createFloatNode(v);
			} 
		catch (Exception e)
			{
			}
		return createStringNode(value);
		}
	
	public ObjectNode createObjectNode()
		{
		return createObjectNode(10);
		}
	
	public Node read(byte array[]) throws IOException
		{
		return read(array,0,array.length);
		}
	
	public Node read(byte array[],int offset,int length) throws IOException
		{
		DataInputStream in=new DataInputStream(new ByteArrayInputStream(array,offset,length));
		Node n= read(in);
		in.close();
		return n;
		}
	
	public Node read(DataInputStream in) throws IOException
		{
		int ord=in.readByte();
		switch(NodeType.values()[ord])
			{
			case NIL:
				{
				return createNilNode();
				}
			case BOOLEAN:
				{
				boolean v= in.readBoolean();
				return createBooleanNode(v);
				}
			case INT:
				{
				int v= in.readInt();
				return createIntNode(v);
				}
			case FLOAT:
				{
				double v= in.readDouble();
				return createFloatNode(v);
				}
			case STRING:
				{
				String s= in.readUTF();
				return createStringNode(s);
				}
			case ARRAY:
				{
				int v= in.readInt();
				ArrayNode n=createArrayNode(v);
				for(int i=0;i< v;++i) n.getList().add( this.read(in) );
				return n;
				}
			case OBJECT:
				{
				int v= in.readInt();
				ObjectNode n=createObjectNode(v);
				for(int i=0;i< v;++i)
					{
					String key= in.readUTF();
					Node value= this.read(in);
					n.getMap().put(key,value);
					}
				return n;
				}
			default:
				{
				throw new IllegalArgumentException("bad node type:"+ord);
				}
			}
		}
	
	public static void parse(DataInputStream in,ContentHandler handler) throws SAXException,IOException
		{
		handler.startDocument();
		parseSax(in,handler);
		handler.endDocument();
		}
	
	private static void parseSax(DataInputStream in,ContentHandler handler) throws SAXException,IOException
		{

		int ord=in.readByte();
		switch(NodeType.values()[ord])
			{
			case NIL:
				{
				handler.startElement("", "", NilNode.TAG, new AttributesImpl());
				handler.endElement("", "", NilNode.TAG);
				break;
				}
			case BOOLEAN:
				{
				boolean v= in.readBoolean();
				handler.startElement("", "",BooleanNode.TAG, new AttributesImpl());
				char ch[]=String.valueOf(v).toCharArray();
				handler.characters(ch, 0, ch.length);
				handler.endElement("", "", BooleanNode.TAG);
				break;
				}
			case INT:
				{
				int v= in.readInt();
				handler.startElement("", "", IntNode.TAG, new AttributesImpl());
				char ch[]=String.valueOf(v).toCharArray();
				handler.characters(ch, 0, ch.length);
				handler.endElement("", "", IntNode.TAG);
				break;
				}
			case FLOAT:
				{
				double v= in.readDouble();
				handler.startElement("", "", FloatNode.TAG, new AttributesImpl());
				char ch[]=String.valueOf(v).toCharArray();
				handler.characters(ch, 0, ch.length);
				handler.endElement("", "", FloatNode.TAG);
				break;
				}
			case STRING:
				{
				String v= in.readUTF();
				handler.startElement("", "",StringNode.TAG, new AttributesImpl());
				char ch[]=String.valueOf(v).toCharArray();
				handler.characters(ch, 0, ch.length);
				handler.endElement("", "", StringNode.TAG);
				break;
				}
			case ARRAY:
				{
				int v= in.readInt();
				AttributesImpl atts=new AttributesImpl();
				atts.addAttribute("", "", "size", "CDATA", String.valueOf(v));
				handler.startElement("", "", ArrayNode.TAG, atts);
				for(int i=0;i< v;++i) parseSax(in,handler);
				handler.endElement("", "", ArrayNode.TAG);
				break;
				}
			case OBJECT:
				{
				int v= in.readInt();
				AttributesImpl atts=new AttributesImpl();
				atts.addAttribute("", "", "size", "CDATA", String.valueOf(v));
				handler.startElement("", "",ObjectNode.TAG, atts);
				for(int i=0;i< v;++i)
					{
					String key= in.readUTF();
					atts.clear();
					atts.addAttribute("", "", ObjectNode.PROP_NAME, "CDATA", key);
					handler.startElement("", "", ObjectNode.PROPERTY, atts);
					parseSax(in,handler);
					handler.endElement("", "",ObjectNode.PROPERTY);
					}
				handler.endElement("", "", ObjectNode.TAG);
				break;
				}
			default:
				{
				throw new IllegalArgumentException("bad node type:"+ord);
				}
			}
		
		}
	}
